#pragma once

#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include <unordered_map>
#include <algorithm>
#include <cstdlib>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/sendfile.h>
#include <sys/wait.h>
#include "Log.hpp"
#include "Util.hpp"

#define SEP ": "
#define WEB_ROOT "wwwroot"
#define HOME_PAGE "index.html"
#define HTTP_VERSION "HTTP/1.0"
#define LINE_END "\r\n"
#define PAGE_404 "404.html"


#define OK 200
#define NOT_FOUND 404

#define OK 200
#define BAD_REQUEST 400
#define NOT_FOUND 404
#define SERVER_ERROR 500

static std::string Code2Desc(int code)
{
    std::string desc;
    switch(code){
        case 200:
            desc = "OK";
            break;
        case 404:
            desc = "Not Found";
            break;
        default:
            break;
    }
    return desc;
}

/*
 * 根据请求当中不同的后缀， 来进行匹配
 * */
static std::string Suffix2Desc(const std::string &suffix){
    static std::unordered_map<std::string, std::string> suffix2desc = {
        {".html", "text/html"},
        {".css", "text/css"},
        {".js", "application/javascript"},
        {".jpg", "application/x-jpg"},
        {".xml", "application/xml"},
    };

    //如果在map当中就返回相应的文件类型
    auto iter = suffix2desc.find(suffix);
    if(iter != suffix2desc.end()){
        return iter->second;
    }
    //没找到就返回文本类型（保底的）
    return "text/html";
}

class HttpRequest{
    public:
        //请求首行
        std::string request_line;
        //请求体当中的每一行放到vector当中， 后续会将key：value进行拆分， 放到map当中
        std::vector<std::string> request_header;
        //空行
        std::string blank;
        //请求正文
        std::string request_body;

        //解析完毕之后的结果
        //请求方法
        std::string method;
        //uri
        std::string uri; //path?args
        //协议版本
        std::string version;

        //请求体当中的每一行， 拆分放到map， key：value
        std::unordered_map<std::string, std::string> header_kv;
        //请求正文长度
        int content_length;
        //请求的路径
        std::string path;
        //后缀
        std::string suffix;
        //查询字符串
        std::string query_string;

        //是否是cgi
        bool cgi;
        //静态文件的大小
        int size;
    public:
        HttpRequest():content_length(0), cgi(false){}
        ~HttpRequest(){}
};

class HttpResponse{
    public:
        //响应首行
        std::string status_line;
        //响应体
        std::vector<std::string> response_header;
        //空行
        std::string blank;
        //响应正文
        std::string response_body;

        //状态码
        int status_code;
        //静态文件打开的文件描述符
        int fd;
    public:
        HttpResponse():blank(LINE_END),status_code(OK),fd(-1){}
        ~HttpResponse(){}
};

//读取请求，分析请求，构建响应
//IO通信
/*
 * 该类为处理TCP连接当中的http请求，分析请求， 构建http响应的类
 *   1.需要接收并处理原生的TCP数据
 *   2.按照HTTP的格式继续解析
 *   3.判断请求意图之后， 构建HTTP响应
 *   4.发送HTTP响应
 *  其中这些动作都是由CallBack类当中的HandlerRequest函数串联起来
 * */
class EndPoint{
    private:
        //TCP新连接的套接字
        int sock;
        //HTTP请求的类对象， HttpRequest in Protocol.hpp
        HttpRequest http_request;
        //HTTP响应的类对象， HttpResponse in Protocol.hpp
        HttpResponse http_response;

        //是否停止， 这个是控制外层是否停止构造响应， 发送响应的
        bool stop;
    private:
        /*
         * 获取请求首行
         *    请求首行放到http_request.request_line当中
         * */
        bool RecvHttpRequestLine(){
            auto &line = http_request.request_line;
            /*
             * 调用工具类当中ReadLine函数， 从新连接的套接字当中读取一行内容， 也就是请求首行
             * */
            if(Util::ReadLine(sock, line) > 0){
                line.resize(line.size()-1);
                LOG(INFO, http_request.request_line);
            }else{
                /*
                 * ==0 或者 <0 都是失败的， 不进行后续的操作了
                 * 如果获取请求首行失败了， 在将stop标志位设置为true， 则就不会针对该请求进行
                 * 响应了， 也就是HandlerRequest 当中不会进行回复响应
                 *         if(!ep->IsStop()){ //一定要注意逻辑关系
                 * */
                stop = true;
            }
            std::cout << "RecvHttpRequestLine: " << stop << std::endl;
            return stop;
        }

        /*
         * 1.获取请求体（循环获取）
         * 2.将请求体的内容放到http_request的request_header当中
         *
         */
        bool RecvHttpRequestHeader()
        {
            std::string line;
            //循环获取请求体
            while(true){
                line.clear();
                if(Util::ReadLine(sock, line) <= 0){
                /*
                 * 如果获取请求首行失败了， 在将stop标志位设置为true， 则就不会针对该请求进行
                 * 响应了， 也就是HandlerRequest 当中不会进行回复响应
                 *         if(!ep->IsStop()){ //一定要注意逻辑关系
                 * */

                    stop = true;
                    break;
                }

                if(line == "\n"){
                    /*
                     * 1.如果发现读到的行是空行， 则请求体就接收完毕了， 则跳出循环
                     * 2.将空行放到http_request.blank当中
                     * */
                    http_request.blank = line;
                    break;
                }

                line.resize(line.size()-1);
                
                /*
                 * 将请求体的内容放到http_request.request_header当中
                 * std::vector<std::string> request_header;
                 *
                 * */
                http_request.request_header.push_back(line);
                LOG(INFO, line);
            }
            std::cout <<"stop debug: " << stop << std::endl;
            return stop;
        }


        /*
         * 1.解析首行
         */
        void ParseHttpRequestLine()
        {
            auto &line = http_request.request_line;

            /*
             * 1.<sstream>库定义了三种类：istringstream、ostringstream和stringstream，分别用来进行流的输入、输出和输入输出操作
             * 2.获取首行当中的请求方法， uri, 协议版本
             * */
            std::stringstream ss(line);
            ss >> http_request.method >> http_request.uri >> http_request.version;
            auto &method = http_request.method;
            /*
             * :toupper : 将method的字母全部变成大写， 重新写入到method当中
             * */
            std::transform(method.begin(), method.end(), method.begin(), ::toupper);
        }

        /*
         * 1.解析请求体
         * 循环进行切割， 将请求体当中的key作为header_kv当中的key， value作为header_kv当中的value
         * 保存在 unordered_map当中
         * */
        void ParseHttpRequestHeader()
        {
            std::string key;
            std::string value;
            for(auto &iter : http_request.request_header)
            {
                //SEP 是 ": "
                if(Util::CutString(iter, key, value, SEP)){
                    http_request.header_kv.insert({key, value});
                }
            }
        }
        
        /*
         * 判断是否需要接收请求正文
         * POST请求 且 请求体当中有content_length
         *
         * 通过在unordered_map当中查找是否有Content-Length
         *    如果有则写到http_request.content_length
         *
         * 返回值：
         *   true : 需要接收
         *   false : 不需要接收
         * */
        bool IsNeedRecvHttpRequestBody()
        {
            auto &method = http_request.method;
            if(method == "POST"){
                auto &header_kv = http_request.header_kv;
                auto iter = header_kv.find("Content-Length");
                if(iter != header_kv.end()){
                    LOG(INFO, "Post Method, Content-Length: "+iter->second);
                    //http_request.content_length 类型为int， 所以需要atoi转化
                    http_request.content_length = atoi(iter->second.c_str());
                    return true;
                }
            }
            return false;
        }


        /*
         * 接收请求正文
         * */
        bool RecvHttpRequestBody(){
            if(IsNeedRecvHttpRequestBody()){
                int content_length = http_request.content_length;
                auto &body = http_request.request_body;

                char ch = 0;
                //读取固定长度的正文信息
                while(content_length){
                    ssize_t s = recv(sock, &ch, 1, 0);
                    if(s > 0){
                        body.push_back(ch);
                        content_length--;
                    }else{
                        //但凡有一次读取失败， 则该请求就不处理了
                        stop = true;
                        break;
                    }
                }
                LOG(INFO, body);
            }
            return stop;
        }


        int ProcessCgi(){
            LOG(INFO, "process cgi mthod!");

            int code = OK;
            //父进程数据
            auto &method = http_request.method;
            auto &query_string =  http_request.query_string; //GET
            auto &body_text = http_request.request_body;     //POST
            auto &bin = http_request.path; //要让子进程执行的目标程序,一定存在
            int content_length = http_request.content_length;
            auto &response_body = http_response.response_body;

            std::string query_string_env;
            std::string method_env;
            std::string content_length_env;

            //站在父进程角度
            /*
             * inpout[0] : 父进程读
             * intput[1] ：子进程写
             * */
            int input[2];
            /*
             * output[0]: 子进程读
             * output[1]: 父进程读
             * */
            int output[2];

            /*
             * 创建两个匿名管道， 供父子进程进行全双工的读写
             * 任意的一个管道创建失败之后， 则不进行后续的处理， 直接返回
             * */
            if(pipe(input) < 0){
                LOG(ERROR, "pipe input error");
                code = SERVER_ERROR;
                return code;
            }

            if(pipe(output) < 0){
                LOG(ERROR, "pipe output error");
                code = SERVER_ERROR;
                return code;
            }

            //新线程，但是从头到尾都只有一个进程，就是httpserver！
            /*
             * 创建一个子进程， 子进程的文件描述符表当中存在
             * input[0], input[1], output[0], output[1]
             * 同理父进程当中也存在哈
             * 父进程                                                子进程
             * input[0]        <-------------------------------     input[1]
             * output[1]       ------------------------------->     output[0]
             * intput[1] ：关闭                                     input[0]： 关闭 
             * output[0] : 关闭                                     output[1]：关闭
             * */
            pid_t pid = fork();
            /*
             * 子进程的逻辑
             *  关闭input[0] : 子进程通过input[1]进行写， 父进程通过input[0]进行读
             *  关闭output[1] : 子进程通过output[0]进行读， 父进程通过output[1]进行写
             * */
            if(pid == 0 ){
                close(input[0]);
                close(output[1]);

                method_env = "METHOD=";
                method_env += method;

                //新增环境变量 METHOD
                putenv((char*)method_env.c_str());

                if(method == "GET"){
                    query_string_env = "QUERY_STRING=";
                    query_string_env += query_string;
                    putenv((char*)query_string_env.c_str());
                    LOG(INFO, "Get Method, Add Query_String Env");
                }
                else if(method == "POST"){
                    content_length_env = "CONTENT_LENGTH=";
                    content_length_env += std::to_string(content_length);
                    putenv((char*)content_length_env.c_str());
                    LOG(INFO, "Post Method, Add Content_Length Env");
                }
                else{
                    //Do Nothing
                }

                //替换成功之后，目标子进程如何得知，对应的读写文件描述符是多少呢?不需要，只要读0， 写1即可
                //站在子进程角度
                //input[1]: 写出  -> 1 -> input[1] 
                //output[0]: 读入 -> 0 -> output[0]
                std::cout << "bin: " << bin << std::endl;

                /*
                 * 将0号文件描述符重定向为output[0]，读取父进程进行的输入信息
                 * 将1号文件描述符重定向为input[1], 将原本标准输出的内容写入管道， 传递给父进程
                 * */
                dup2(output[0], 0);
                dup2(input[1], 1);

                //进程程序替换为cgi程序
                execl(bin.c_str(), bin.c_str(), nullptr);
                exit(1);
            }
            /*
             * 创建子进程失败了， 则后面的动作都不进行了， 返回404
             * */
            else if(pid < 0){ //error
                LOG(ERROR, "fork error!");
                return 404;
            }
            /*
             * 父进程执行的逻辑
             * 关闭input[1] : 父进程通过input[0]进行读， 子进程通过input[1]进行写
             * 关闭output[0] : 父进程通过output[1]进行写， 子进程通过output[0]进行读
             * */
            else{ //parent
                close(input[1]);
                close(output[0]);

                /*
                 * 如果请求方法是POST， 将请求正文传递给cgi程序
                 * */
                if(method == "POST"){
                    const char *start = body_text.c_str();
                    int total = 0;
                    int size = 0;
                    while(total < content_length && (size= write(output[1], start+total, body_text.size()-total)) > 0){
                        total += size;
                    }
                }

                /*
                 * 获取执行结果， 放到响应正文当中
                 * */
                char ch = 0;
                while(read(input[0], &ch, 1) > 0){
                    response_body.push_back(ch);
                }

                /*
                 * 进程等待， 防止子进程（cgi）程序变成僵尸进程
                 * */
                int status = 0;
                pid_t ret = waitpid(pid, &status, 0);
                if(ret == pid){
                    if(WIFEXITED(status)){
                        if(WEXITSTATUS(status) == 0){
                            code = OK;
                        }
                        else{
                            code = BAD_REQUEST;
                        }
                    }
                    else{
                        code = SERVER_ERROR;
                    }
                }

                close(input[0]);
                close(output[1]);
            }
            return code;
        }


        void HandlerError(std::string page)
        {
            std::cout << "debug: " << page << std::endl;
            http_request.cgi = false;
            //要给用户返回对应的404页面
            http_response.fd = open(page.c_str(), O_RDONLY);
            if(http_response.fd > 0){
                struct stat st;
                stat(page.c_str(), &st);
                http_request.size = st.st_size;

                std::string line = "Content-Type: text/html";
                line += LINE_END;
                http_response.response_header.push_back(line);

                line = "Content-Length: ";
                line += std::to_string(st.st_size);
                line += LINE_END;
                http_response.response_header.push_back(line);
            }
        }
        /*
         * 非cgi的情况， 返回一个网页
         * 这个函数就是将文件打开， 将文件描述符放到响应的fd当中， 后续在发送响应的时候， 进行读
         * http_response.fd
         * */
        int ProcessNonCgi(){
            http_response.fd = open(http_request.path.c_str(), O_RDONLY);
            if(http_response.fd >= 0){
                LOG(INFO, http_request.path + " open success!");
                return OK;
            }
            return NOT_FOUND;
        }

        /*
         * 该函数是构建正常的响应
         * */
        void BuildOkResponse()
        {
            //设置响应体的Content-Type
            std::string line = "Content-Type: ";
            line += Suffix2Desc(http_request.suffix);
            line += LINE_END;
            http_response.response_header.push_back(line);

            //设置响应体当中的Content-Length， 正文长度
            line = "Content-Length: ";
            //如果是cgi， 则返回响应正文的成都
            //如果不是cgi， 则返回访问文件的大小（前面赋值过了）
            if(http_request.cgi){
                line += std::to_string(http_response.response_body.size());
            }else{
                line += std::to_string(http_request.size); //Get
            }
            //设置空行
            line += LINE_END;
            //设置响应报头
            http_response.response_header.push_back(line);
        }

        /*
         * 构建响应
         * 
         * */
        void BuildHttpResponseHelper(){
            
            auto &code = http_response.status_code;
            //构建状态行
            auto &status_line = http_response.status_line;
            status_line += HTTP_VERSION;
            status_line += " ";
            status_line += std::to_string(code);
            status_line += " ";
            status_line += Code2Desc(code);
            status_line += LINE_END;

            //构建响应正文,可能包括响应报头
            std::string path = WEB_ROOT;
            path += "/";
            switch(code){
                case OK:
                    BuildOkResponse();
                    break;
                case NOT_FOUND:
                    path += PAGE_404;
                    HandlerError(path);
                    break;
                case BAD_REQUEST:
                    path += PAGE_404;
                    HandlerError(path);
                    break;
                case SERVER_ERROR:
                    path += PAGE_404;
                    HandlerError(path);
                    break;
//                case 500:
//                  HandlerError(PAGE_500);
//                    break;
                default:
                    break;
            }
        }
    public:
        EndPoint(int _sock):sock(_sock), stop(false)
        {}
        bool IsStop()
        {
            return stop;
        }


        /*
         * void RecvHttpRequest()
         * 
         * 接收和处理Http请求的函数， 也是Callback类当中调用的总接口
         *    1.获取请求首行 且 请求体
         *    2.如果两者都正常获取， 则进行解析
         *      2.1 解析请求首行
         *      2.2 解析请求体
         *      2.3 构造响应头部
         * */
        void RecvHttpRequest()
        {
            // || 短路求值
            if( (!RecvHttpRequestLine()) && (!RecvHttpRequestHeader()) ){
                ParseHttpRequestLine();
                ParseHttpRequestHeader();
                //接收请求正文
                //GET是没有请求正文的
                //POST理论上有
                RecvHttpRequestBody();
            }
        }

        //请求已经全部读完,即可以直接构建响应了
        void BuildHttpResponse() {
            //请求已经全部读完,即可以直接构建响应了
            std::string _path;
            struct stat st;
            std::size_t found = 0;
            auto &code = http_response.status_code;
            //如果请求方法并不是GET并且不是POST，则认为是非法请求（本质上我们程序只在处理这两种）
            if(http_request.method != "GET" && http_request.method != "POST"){
                //非法请求
                std::cout << "method: " << http_request.method << std::endl;
                LOG(WARNING, "method is not right");
                code = BAD_REQUEST;
                goto END;
            }

            /*
             * 1.GET请求处理
             *   需要判断是否有查询字符串， 也就是url当中是否有?以及后续内容
             * */
            if(http_request.method == "GET"){
                //在uri当中查找是否有?
                size_t pos = http_request.uri.find('?');
                if(pos != std::string::npos){
                    //有？， 需要切割， 拿到PATH和查询字符串
                    Util::CutString(http_request.uri, http_request.path, http_request.query_string, "?");
                    //激活cgi
                    // CGI(Common Gateway Interface)是外部应用程序与Web服务器交互的一个标准接口
                    //   CGI工作的主要流程是：1.一个用户请求激活一个CGI应用程序；2.CGI应用程序将交互主页中用户输入的信息提取出来；3.将用户输入的信息传给服 务器主机应用程序（如数据库查询)；4.将服务器处理结果通过HTML文件返回给用户；5.CGI进程结束
                    http_request.cgi = true;
                }else{
                    //没有查询字符串， 只是PATH
                    http_request.path = http_request.uri;
                }
            }else if(http_request.method == "POST"){
                /*
                 * POST方法
                 *  默认激活cgi
                 *  post方法的uri当中没有查询字符串， 可以认为直接就是路径
                 * */
                http_request.cgi = true;
                http_request.path = http_request.uri;
            }else{
                //Do Nothing
            }


            //下面三行代码是在路径前面加上逻辑根目录
            //eg : path 本身为 "/a/b"
            //     改完为 "wwwroot/a/b"
            _path = http_request.path;
            //WEB_ROOT 是逻辑根目录 "wwwroot"
            http_request.path = WEB_ROOT;
            http_request.path += _path;

            //如果路径后为"/", 则给后面加上index.html
            if(http_request.path[http_request.path.size()-1] == '/'){
                http_request.path += HOME_PAGE;
            }

            /*
             * 判断请求当中请求的资源是否存在
             * */
            if(stat(http_request.path.c_str(), &st) == 0){
                //说明资源是存在的
                //如果请求的是目录则返回值主页 index.html
                if(S_ISDIR(st.st_mode)){
                    //说明请求的资源是一个目录，不被允许的,需要做一下相关处理
                    //虽然是一个目录，但是绝对不会以/结尾！
                    http_request.path += "/";
                    http_request.path += HOME_PAGE;
                    stat(http_request.path.c_str(), &st);
                }

                /*
                 * st.st_mode&S_IXUSR : 判断文件所有者是否具有可执行权限
                 * st.st_mode&S_IXGRP : 判断用户组是否具有可执行权限
                 * st.st_mode&S_IXOTH ：判断其他用户是否具有可执行权限
                 * 如果由任意一个用户具有可执行权限， 则激活cgi, 进行处理
                 * */
                if( (st.st_mode&S_IXUSR) || (st.st_mode&S_IXGRP) || (st.st_mode&S_IXOTH) ){
                    //特殊处理
                    http_request.cgi = true;
                }
                //保存文件大小
                http_request.size = st.st_size;
            }else{
                //说明资源是不存在的
                LOG(WARNING, http_request.path + " Not Found");
                code = NOT_FOUND;
                goto END;
            }


            //rfind(str)是从字符串右侧开始匹配str，并返回在字符串中的位置（下标）
            found = http_request.path.rfind(".");
            //如果没有找到"."
            if(found == std::string::npos){
                http_request.suffix = ".html";
            }else{
                http_request.suffix = http_request.path.substr(found);
            }

            if(http_request.cgi){
                code = ProcessCgi(); //执行目标程序，拿到结果:http_response.response_body;
            }
            else{
                //1. 目标网页一定是存在的
                //2. 返回并不是单单返回网页，而是要构建HTTP响应
                code = ProcessNonCgi(); //简单的网页返回，返回静态网页,只需要打开即可
            }
END:
            BuildHttpResponseHelper(); //状态行填充了，响应报头也有了， 空行也有了，正文有了
        }

        /*
         * 发送响应
         * */
        void SendHttpResponse()
        {
            //发送首行
            send(sock, http_response.status_line.c_str(), http_response.status_line.size(), 0);
            for(auto iter : http_response.response_header){
                send(sock, iter.c_str(), iter.size(), 0);
            }

            //发送空行
            send(sock, http_response.blank.c_str(), http_response.blank.size(), 0);
            //fd, response_body
            //发送响应正文
            if(http_request.cgi){
                /*
                 * 如果是cgi的情况， 则返回子进程执行的结果
                 * */
                auto &response_body = http_response.response_body;
                size_t size = 0;
                size_t total = 0;
                const char *start = response_body.c_str();
                while( total < response_body.size() 
                        && (size = send(sock, start + total, response_body.size() - total, 0)) > 0){
                    total += size;
                }
            }else{
                std::cout << ".............."<< http_response.fd << std::endl;
                std::cout << ".............."<< http_request.size << std::endl;
                //发送文件(内核直接处理了， 不用在用户缓冲区和内核缓冲区当中进行拷贝)
                sendfile(sock, http_response.fd, nullptr, http_request.size);
                //关闭文件描述符（这个就是静态网页打开的文件描述符）
                close(http_response.fd);
            }
        }
        ~EndPoint(){
            //当线程池的线程执行结束之后， 调用关闭tcp新连接的套接字（短连接的玩法）
            close(sock);
        }
};


/*
 * CallBack 类作为业务处理的回调类， 其实可以理解为业务处理的入口位置
 *   由该类实例化EndPoint类的对象， 来进行浏览器请求的解析， 处理， 以及响应的回复
 * */
class CallBack{
    public:
        CallBack(){}

        /*
         * ()重载
         * 调用函数HandlerRequest，线程就是通过这个函数进入到业务处理的 in Task.hpp
         * sock 是 tcp新连接的文件描述符
         * */
        void operator()(int sock){
            HandlerRequest(sock);
        }


        /*
         * 这个函数是线程池当中线程处理业务的总入口函数
         * 1.实例化EndPoint类的对象， 来进行浏览器请求的解析， 处理， 以及响应的回复
         * */
        void HandlerRequest(int sock){
            LOG(INFO, "Hander Request Begin");
#ifdef DEBUG
            //For Test
            char buffer[4096];
            recv(sock, buffer, sizeof(buffer), 0);
            std::cout << "-------------begin----------------" << std::endl;
            std::cout << buffer << std::endl;
            std::cout << "-------------end----------------" << std::endl;
#else 

            /*
             * EndPoint in Protocol.hpp
             * */
            EndPoint *ep = new EndPoint(sock);
            // 接收浏览器请求
            ep->RecvHttpRequest();

            /*
             * 接收浏览器请求成功
             *    构造响应
             *    发送响应
             * */
            if(!ep->IsStop()){ //一定要注意逻辑关系
                /*
                 * 代码能走到这里， 说明请求正常接收回来了， 
                 *   一定存在请求首行+请求体
                 *   可能存在请求正文（看是否是POST+是否有Content-Length）
                 * */
                LOG(INFO, "Recv No Error, Begin Build And Send");
                //构造相应+发送相应
                ep->BuildHttpResponse();
                ep->SendHttpResponse();
            }else{
            /*
             * 接收失败了， 日志继续输出 
             * */
                LOG(WARNING, "Recv Error, Stop Build And Send");
            }
            //销毁类实例化指针
            delete ep;
#endif
            //日志记录
            LOG(INFO, "Hander Request End");
            /*
             * 1.线程池的线程 需要注意， 当该函数处理完毕了， 说明线程池当中的线程针对该请求的处理就结束了
             * 2.tcp的连接也被释放了（短连接）
             * */
        }

        ~CallBack(){}
};
